<script setup>
    import { ref, onMounted } from 'vue';
    import $ from 'jquery';
    import { fabric } from 'fabric';

    import "virtual:svg-icons-register";

    import SvgIcon from './SvgIcon.vue';

    import { markRaw } from 'vue'
    import { ElMessageBox } from 'element-plus'
    import { WarningFilled } from '@element-plus/icons-vue'


    let snakeCanvas = null;

    const pageData = ref({score: 0, prescore: 0, bt: 'badge_A'});


    function spinning(){
        let c = 40, sum = 0;
        for(let i=1; i <= c; i++){
            sum += i;
        }
        return sum;
    }

    const levelMap = {
        1: { ds: 30, bt: 'badge_A', fc: 20, speed: 340},
        2: { ds: 40, bt: 'badge_B', fc: 24, speed: 290},
        3: { ds: 50, bt: 'badge_C', fc: 30, speed: 260},
        4: { ds: 60, bt: 'badge_D', speed: 200}
        
    }
    function debounce(callback, delay) {
        let timeout = 0;
        return e => {
            //input 一直触发，就清除上一次的定时器，防止执行目标函数，直到事件不触发事件，最后一个定时器没有清除，delay 时间后就会执定时器，就确保了目标函数只执行一次。
            if(timeout){
                console.log('清除', timeout, new Date());
                clearTimeout(timeout);
            }
            timeout = setTimeout(() => { callback(e); }, levelMap[game.level].speed);
            console.log('新的', timeout, e.target.value, new Date());
        };
    }
    
    const grid = {
        dx: 64,
        dy: 40,
        xc: 22,
        yc: 18,
        sw: 30,

        cells: [],

        kinit: function(){
            if(this.cells.length < 1){
                for (let i = 0; i < this.xc; i++) {
                    for (let j = 0; j < this.yc; j++) {
                        this.cells.push([i, j])
                    } 
                }
            }
            this.kinit = function(){};
        },

        lineOptions: {
            stroke: '#018371',
            strokeWidth: 1,
            selectable: false
        },

        draw: function(localCanvas){
            for(let i=0; i<=this.xc; i++){
                localCanvas.add(new fabric.Line([i*this.sw+this.dx, this.dy, i*this.sw+this.dx, this.yc*this.sw+this.dy], this.lineOptions));
            }

            for(let i=0; i<=this.yc; i++){
                localCanvas.add(new fabric.Line([this.dx, i*this.sw+this.dy, this.xc*this.sw+this.dx, i*this.sw+this.dy], this.lineOptions));
            }
        }
    }

    const initBody = [
        [6, 15],
        [5, 15],
        [4, 15],
        [3, 15],
        [2, 15],
    ]
    const snake = {
        body: initBody.concat(),

        dir: 'right',
        canvasElems: [],
        
        headOption: {
            fill: '#ff5733',
            // fill: '#019c6d',
            rx: 2,
            ry: 2,
            selectable: false
        },

        bodyOption: {
            // fill: '#e4ff33',
            fill: '#d0ce01',
            rx: 2,
            ry: 2,
            selectable: false
        },

        food: [],
        flashing: 1,
        
        foodOption: {
            fill: '#FFB433',
            // fill: '#019c6d',
            rx: 2,
            ry: 2,
            selectable: false,
        },
        
        makeFood: function(){
            grid.kinit();

            let [x, y] = grid.cells[(Math.random() * grid.cells.length) | 0];
            if( this.body.some( item => item[0] === x && item[1] === y) ){
                return this.makeFood();
            }
            return [x, y];
        },
        
        draw: function(bodyDup, [x, y], localCanvas){
            /* if( bodyDup[0][0] < 0 || bodyDup[0][0] >= grid.xc){ return; }
            if( bodyDup[0][1] < 0 || bodyDup[0][1] >= grid.yc){ return; } */

            if(this.canvasElems.length > 0){
                while(this.canvasElems.length > 0){
                    localCanvas.remove(this.canvasElems.pop());
                }
            }
            
            this.canvasElems.push(new fabric.Rect({
                left: x * grid.sw + grid.dx + this.flashing,
                top:  y * grid.sw + grid.dy + this.flashing,
                width: grid.sw - this.flashing*2,
                height: grid.sw - this.flashing*2,
                ...this.foodOption
            }));    
            localCanvas.add(this.canvasElems[0]);
            this.flashing ^= 1;
            
            // draw head
            this.canvasElems.push(new fabric.Rect({
                left: bodyDup[0][0] * grid.sw + grid.dx,
                top:  bodyDup[0][1] * grid.sw + grid.dy,
                width: grid.sw,
                height: grid.sw,
                ...this.headOption
            }));
            localCanvas.add(this.canvasElems[1]);
            
            
            for(let i=1; i<bodyDup.length; i++){
                    this.canvasElems.push(new fabric.Rect({
                        left: bodyDup[i][0] * grid.sw + grid.dx,
                        top:  bodyDup[i][1] * grid.sw + grid.dy,
                        width: grid.sw,
                        height: grid.sw,
                        ...this.bodyOption
                    }));
                    localCanvas.add(this.canvasElems[1+i]);
            }

            localCanvas.renderAll();
        },

        move: function(bodyDup){
            let [x, y] = bodyDup[0]; // fetch head;
            
            switch(this.dir){
                case 'left':
                    x--;
                    break;
                case 'up':
                    y--;
                    break;
                case 'right':
                    x++;
                    break;
                case 'down':
                    y++;
                    break;
            }
            
            let food = undefined;
            if(x === snake.food[0] && y === snake.food[1]){
                food = this.makeFood();
                game.addScore();
            } else {
                bodyDup.pop();
            }

            if(this.hitBody(bodyDup, [x, y]) || this.hitBorder([x, y])){
                this.stopAndReset();
                return;
            }

            bodyDup.unshift([x, y]);
            return {body: bodyDup, food: food};
        },

        hitBody: function(body, [x, y]){
            return body.some( item => item[0] === x && item[1] === y );
        },
        
        hitBorder: function([x, y]){
            if(x < 0 || x >= grid.xc || y < 0 || y >= grid.yc){
                console.log('over board');
                return true;
            }
        },

        reset: function(){
            this.body = initBody.concat();
            this.dir  = 'right';
            this.food = this.makeFood();
        },
        
        stopAndReset: function(){
            game.pause();
            this.reset();
            game.resetScore();
            game.gameAlert();
        }
    }

    const game = {
        keyEventCallback: function(event){
            if( !game.gameTimer && event.keyCode !== 32 ){
                console.log('no timer defined');
                return; 
            }

            switch(event.keyCode){
                case 37:
                    if(snake.dir !== 'right') snake.dir = 'left';
                    break;
                case 38:
                    if(snake.dir !== 'down')  snake.dir = 'up';
                    break;
                case 39:
                    if(snake.dir !== 'left')  snake.dir = 'right';
                    break;
                case 40:
                    if(snake.dir !== 'up')    snake.dir = 'down';
                    break;
                case 32: // space
                    if(!switcher.status){
                        game.resume();
                    } else {
                        game.pause();
                    }
                    break;
            }
        },

        addScore: function(){
            let ll = this.level;
            pageData.value.score += levelMap[ll].ds;
            this.fc++;
            if( this.fc === levelMap[ll].fc && ll < 4 ){
                ll += 1;
                this.level = ll;
                this.fc = 0;
                pageData.value.bt = levelMap[ll].bt;

                this.stop();
                spinning();
                this.start();
            }

            if(pageData.value.score > pageData.value.prescore){
                pageData.value.prescore = pageData.value.score;
            }
        },

        resetScore: function(){
            pageData.value.score = 0;
            let ll = 1;
            this.level = ll;
            pageData.value.bt = levelMap[ll].bt;
            this.fc = 0;
        },
        
        keyListener: function(){
            document.onkeydown = debounce(this.keyEventCallback, 180)
        },
        
        level: 1,
        fc: 0,
        
        gameTimer: null,
        
        start: function(){
            this.gameTimer = setInterval(() => {
                let bodyDup = snake.body.concat();
                let food    = snake.food;

                let rst = snake.move( bodyDup );
                
                if(rst && rst.body){
                    if(rst.food){
                        snake.food  = rst.food;
                        food        = rst.food;
                    }
                    bodyDup = rst.body;
                    snake.draw(bodyDup, food, snakeCanvas);
                    snake.body = bodyDup;
                }
            }, levelMap[this.level].speed);
        },
        
        stop: function(){
            if(this.gameTimer){
                clearInterval(this.gameTimer);
                this.gameTimer = null;
            }
        },

        pause: function(){
            game.stop();
            switcher.stopStyle();
        },

        resume: function(){
            game.start();
            switcher.activeStyle();
        },
        
        gameAlert: function(message){   
            // alert('Game Over!');
            if(!message){
                message = 'GAME OVER'
            }

            ElMessageBox.confirm(
                message, 'WARNING',
                {
                    type: 'warning',
                    showCancelButton: false, 
                    showConfirmButton: false,
                    closeOnPressEscape: true,
                    closeOnClickModal: true,
                    icon: markRaw(WarningFilled),
                    customClass: 'non-centered-message-box'
                }
            )
        },

        ready: function(){
            this.keyListener();

            grid.draw(snakeCanvas);
            snake.food = snake.makeFood();
        }
    }
    
    const switcher = {
        status:  false,
        
        click: function(){
            const switchbg      = $('#switchbg');
            const switchitem    = $('#switchitem');
            
            // have to use status like this (switcher.status) for coordinating with the usage in transitionHandler
            switcher.status = !switcher.status;
            if (switcher.status) {
                switchbg.css({'backgroundColor': '#000'});
                switchitem.css({'backgroundColor': '#fff'});
                game.start();
            } else {
                switchbg.css({'backgroundColor': '#fff'});
                switchitem.css({'backgroundColor': '#000'});
                game.stop();
            }

            switchbg.one('transitionend', switcher.transitionHandler);
            switchbg.css('width', '148px');
        },

        transitionHandler: function(event){
            const switchbg = $('#switchbg');
            // cannot use 'this' in dom listener callback, like this.status
            if (switcher.status) {
                switchbg.css('right', '');
                switchbg.css('left',  '12px');
                switchbg.css('width', '74px');
            } else {
                switchbg.css('left',  '');
                switchbg.css('right', '12px');
                switchbg.css('width', '74px');
            }
        },
        
        stopStyle: function(){
            $('#switchbg').css('backgroundColor', '#fff');
            $('#switchbg').css('left',  '');
            $('#switchbg').css('right', '12px');
            $('#switchbg').css('width', '74px');
            
            $('#switchitem').css('backgroundColor', '#000');
            switcher.status = false;
        },

        activeStyle: function(){
            $('#switchbg').css('backgroundColor', '#000');
            $('#switchbg').css('left',  '12px');
            $('#switchbg').css('right', '');
            $('#switchbg').css('width', '74px');
            
            $('#switchitem').css('backgroundColor', '#fff');
            switcher.status = true;
        }
    }


    onMounted(() => {
        const snakeContainerDom          = $('#snake_container');
        const winHeight                  = window.innerHeight * 0.98;
        snakeContainerDom.height(winHeight);

        const snakeCanvasWidth           = grid.xc * grid.sw + 2 * grid.dx;
        const snakeCanvasHeight          = grid.yc * grid.sw + 2 * grid.dy;
        
        const switchitemDom              = $('#switchitem');
        switchitemDom.on('click', switcher.click);

        snakeCanvas = new fabric.Canvas('snake_canvas');
        snakeCanvas.setDimensions({ width: snakeCanvasWidth, height: snakeCanvasHeight });
        
        game.ready();
        // game.gameAlert();
    })
</script>

<template>
    <div>
        <div id="box">
            <div class="switch">
                <div class="item" id="switchitem">
                    <div class="bg" id="switchbg"></div>
                    <div class="text1">ON</div>
                    <div class="text2">OFF</div>
                </div>
            </div>
        </div>
        <div id="badgeBox">
            <span class="c1span">
                <SvgIcon name="crown" size="2.1em" :style="{top: '-8px'}"/>
                <span :style="{color: '#fce041', fontSize: '1em', fontWeight: '900', minWidth: '30px', position: 'absolute', display: 'inline-block', marginTop: '5px', marginLeft: '10px'}">{{ pageData.prescore }}</span>
            </span>

            <span class="c2span">
                <SvgIcon name="badge" size="2.4em"/>
                <span :style="{color: '#fce041', fontSize: '1em', fontWeight: '900', position: 'absolute', display: 'inline-block', marginTop: '5px', marginLeft: '3px'}">{{ pageData.score }}</span>
            </span>

            <span class="c3span">
                <SvgIcon :name="pageData.bt" size="2.3em" :style="{top: '-2px'}"/>
            </span>
        </div>
        <div id="snake_container">
            <canvas id="snake_canvas"></canvas>
        </div>
    </div>
</template>


<style lang="scss">
    div.non-centered-message-box {
        // background-color: #fedcdc;
        background-color: #fdf2e5;
        padding-left: 20px;
        position: absolute;
        left: 38%;
        top: 5%;
        
        div.el-message-box__title {
            font-weight: 900;
            // color: #f56c6c;
            color: #e6a23c;
        }

        div.el-message-box__message{
            color: #e6a23c;
            font-weight:600;
        }
    }

    #snake_container {
        display: flex;
        justify-content: center;
        background: rgba(0, 0, 0, 0.66);
        border-radius: 5px;
    }

    #snake_canvas {
        margin-top: 36px;
        background-color: #181818;
        border-top: 8px solid red;
        border-radius: 5px;
        border-radius: 6px 6px 8px 8px;
        box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22);
    }

    #badgeBox{
        position: absolute;
        margin-top: 35px;
        right: 3.9%;

        .c2span {
            display: inline-block;
            margin-left: 55px;
        }
        
        .c3span {
            margin-left: 45px;
        }
    }

    #box {
      z-index: 99;
      padding: 20px;
      margin-top: 14px;
      margin-left: 45px;
      position: absolute;
      
      .switch {
        width: 180px;
        .item {
          width: 100%;
          height: 48px;
          background-color: #000;
          border-radius: 140px;
          font-size: 16px;
          padding: 20px 30px;
          box-sizing: border-box;
          position: relative;
          box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22);
          .bg {
            box-sizing: border-box;
            width: 74px;
            height: 32px;
            border-radius: 90px;
            background-color: #fff;
            position: absolute;
            top: 8px;
            right: 12px;
            transition: width 0.3s;
          }
          .text1 {
            color: #fff;
            font-weight: 800;
            position: absolute;
            top: 13px;
            left: 22px;
          }
          .text2 {
            color: #000;
            font-weight: 800;
            position: absolute;
            top: 13px;
            right: 22px;
          }
        }
      }
    }
</style>